#include "StdAfx.h"
#include "CPortSetupDlg.h"

#include <functional>
#include <algorithm>

namespace
{
	static const wchar_t *g_PortBaudrate[] = 
	{
		L"9600",
		L"115200"
	};

	static const GUID g_SerialBusGuid = 
	{
		0x4D36E978, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 
	};

	struct SortByName : public std::binary_function<const USB_DevInfo &, const USB_DevInfo &, bool>
	{
		INLINE result_type operator()(first_argument_type dev1,
			second_argument_type dev2) const throw()
		{
			// "COM" prefix length = 3
			ATLASSERT(dev1.name.length() > 3 && dev2.name.length() > 3);
			return _tstol(&dev1.name[3]) < _tstol(&dev2.name[3]);
		}
	};

	struct FindByName : public std::unary_function<const USB_DevInfo &, bool>  
	{
		explicit INLINE FindByName(const TCHAR *name) 
			: m_name(name) 
		{
		}

		INLINE result_type operator()(argument_type dev) const throw() {
			return (dev.name.compare(m_name) == 0);
		}

		std::wstring m_name;
	};

	bool GetDeviceRegistryName(const CString &reg_name, CString &name, const HKEY hkey)
	{
		TCHAR name_buf[MAX_PATH] = {0};
		DWORD name_buf_size = sizeof(name_buf);
		DWORD type = 0;

		if ((::RegQueryValueEx(hkey, 
			reg_name.GetString(), 
			0,
			&type,
			reinterpret_cast<LPBYTE>(&name_buf[0]), 
			&name_buf_size) == ERROR_SUCCESS) && (type == REG_SZ)) 
		{
			name.SetString(name_buf);
			return true;
		}

		return false;
	}

	bool GetDeviceRegistryProperty(CString &description, DWORD property_flag, const USB_DevInfo* usb_dev_info)
	{
		TCHAR description_buffer[MAX_PATH] = { 0 };
		DWORD data_type = 0;
		DWORD need_size = 0;

		if (::SetupDiGetDeviceRegistryProperty(usb_dev_info->hdevinfo,
			const_cast<SP_DEVINFO_DATA*>(&usb_dev_info->spdevinfo_data), 
			property_flag, 
			&data_type, 
			reinterpret_cast<PBYTE>(description_buffer), 
			sizeof(description_buffer),
			&need_size) != FALSE) 
		{
			description.SetString(description_buffer);
			return true;
		}
		return false;
	}

	bool GetSerialDeviceInfo(std::vector<USB_DevInfo> &devinfo_list)
	{
		HDEVINFO hdevinfo = INVALID_HANDLE_VALUE;
		bool found = FALSE;
		DWORD count = 0;

		ATLTRACE(L"%s \n", L"GetDeviceRegistryProperty");

		hdevinfo = ::SetupDiGetClassDevs(&g_SerialBusGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_PROFILE);
		if (hdevinfo == INVALID_HANDLE_VALUE)
			return false;

		do 
		{
			SP_DEVINFO_DATA sp_devinfo_data = { 0 };	
			sp_devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA);
			for (DWORD i = 0 ; found = (::SetupDiEnumDeviceInfo(hdevinfo, i, &sp_devinfo_data) != FALSE) ; ++i)
			{
				HKEY hkey = ::SetupDiOpenDevRegKey(hdevinfo, 
					&sp_devinfo_data, 
					DICS_FLAG_GLOBAL,
					0, 
					DIREG_DEV, 
					KEY_QUERY_VALUE);

				if (hkey != NULL)
				{
					CString port_name;
					CString str;
					if (GetDeviceRegistryName(TEXT("PortName"), port_name, hkey))
					{
						#if 0
						if (GetDeviceRegistryProperty(str, SPDRP_DEVICEDESC, &info))
						{
							if (str.Find(L"Bluetooth") != -1)
								continue;
						}
						#endif
						if (port_name.Find(TEXT("COM")) != -1)
						{
							devinfo_list.push_back(USB_DevInfo(hdevinfo, sp_devinfo_data, hkey, port_name.GetBuffer()));
							ATLTRACE(L"Find %s \n", port_name.GetBuffer());
						}
					}
				}
				else
				{
					break;
				}
			}
		} while (found != false);

		return devinfo_list.size() ? true : false;
	}

	const wchar_t* DumpComCapaSubType(DWORD subtype)
	{
		switch (subtype)
		{
		case PST_FAX:
			return L"FAX device";
		case PST_LAT:
			return L"LAT protocol";
		case PST_MODEM:
			return L"Modem device";
		case PST_NETWORK_BRIDGE:
			return L"PST_PARALLELPORT";
		case PST_PARALLELPORT:
			return L"Parallel port";
		case PST_RS232:
			return L"RS-232 serial port";
		case PST_RS422:
			return L"RS-422 port";
		case PST_RS423:
			return L"RS-423 port";
		case PST_RS449:
			return L"RS-449 port";
		case PST_SCANNER:
			return L"Scanner device";
		case PST_TCPIP_TELNET:
			return L"TCP/IP Telnet protocol";
		case PST_UNSPECIFIED:
			return L"Unspecified";
		case PST_X25:
			return L"X.25 standards";
		}
		return L"Unknow";
	}

	void DumpComCapaBaud(CComboBox &box, DWORD max_baurarte)
	{
		if (max_baurarte & BAUD_075)
			box.AddString(L"75 bps");
		if (max_baurarte & BAUD_110)
			box.AddString(L"110 bps");
		if (max_baurarte & BAUD_134_5)
			box.AddString(L"134.5 bps");
		if (max_baurarte & BAUD_150)
			box.AddString(L"150 bps");
		if (max_baurarte & BAUD_300)
			box.AddString(L"300 bps");
		if (max_baurarte & BAUD_300)
			box.AddString(L"300 bps");
		if (max_baurarte & BAUD_600)
			box.AddString(L"600 bps");
		if (max_baurarte & BAUD_1200)
			box.AddString(L"1200 bps");
		if (max_baurarte & BAUD_1800)
			box.AddString(L"1800 bps");
		if (max_baurarte & BAUD_2400)
			box.AddString(L"2400 bps");
		if (max_baurarte & BAUD_4800)
			box.AddString(L"7200 bps");
		if (max_baurarte & BAUD_7200)
			box.AddString(L"7200 bps");
		if (max_baurarte & BAUD_9600)
			box.AddString(L"9600 bps");
		if (max_baurarte & BAUD_14400)
			box.AddString(L"14400 bps");
		if (max_baurarte & BAUD_19200)
			box.AddString(L"19200 bps");
		if (max_baurarte & BAUD_38400)
			box.AddString(L"38400 bps");
		if (max_baurarte & BAUD_56K)
			box.AddString(L"56K bps");
		if (max_baurarte & BAUD_57600)
			box.AddString(L"57600 bps");
		if (max_baurarte & BAUD_115200)
			box.AddString(L"115200 bps");
		if (max_baurarte & BAUD_128K)
			box.AddString(L"128K bps");
		if (max_baurarte & BAUD_USER)
			box.AddString(L"Programable");
	}

	void DumpComCapaSettableParams(CComboBox &box, DWORD settable_params)
	{
		if (settable_params & SP_BAUD)
			box.AddString(L"Baudrate");
		if (settable_params & SP_DATABITS)
			box.AddString(L"Data bits");
		if (settable_params & SP_HANDSHAKING)
			box.AddString(L"Handshaking (flow control)");
		if (settable_params & SP_PARITY)
			box.AddString(L"Parity");
		if (settable_params & SP_PARITY_CHECK)
			box.AddString(L"Parity checking");
		if (settable_params & SP_RLSD)
			box.AddString(L"Receive Line Signal Detect");
		if (settable_params & SP_STOPBITS)
			box.AddString(L"Stop bits");
	}

	void DumpComCapaProvCapabilities(CComboBox &box, DWORD capas)
	{
		if (capas & PCF_16BITMODE)
			box.AddString(L"Special 16-bit mode is supported");
		if (capas & PCF_DTRDSR)
			box.AddString(L"DTR (Data Terminal Ready) and DSR (Data Set Ready) are supported");
		if (capas & PCF_INTTIMEOUTS)
			box.AddString(L"Interval timeouts are supported");
		if (capas & PCF_PARITY_CHECK)
			box.AddString(L"Parity checking is supported");
		if (capas & PCF_RLSD)
			box.AddString(L"RLSD (Receive Line Signal Detect) is supported");
		if (capas & PCF_RTSCTS)
			box.AddString(L"RTS (Request to Send) and CTS (Clear to Send) are supported");
		if (capas & PCF_SETXCHAR)
			box.AddString(L"XON and XOFF are supported");
		if (capas & PCF_SPECIALCHARS)
			box.AddString(L"Special character support is provided");
		if (capas & PCF_TOTALTIMEOUTS)
			box.AddString(L"Total elapsed timeouts are supported");
		if (capas & PCF_XONXOFF)
			box.AddString(L"XON and XOFF flow control are supported");
	}
}

bool CPortSetupDlg::GetComCapability(USB_DevInfo *dev_info)
{
	static SECURITY_ATTRIBUTES se_attr = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
	COMMPROP prop = { 0 };
	std::wstring name_str(L"\\\\.\\");
	name_str += dev_info->name;
	HANDLE handle = ::CreateFile(name_str.c_str(),
		GENERIC_READ | GENERIC_WRITE, 0, &se_attr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (handle == INVALID_HANDLE_VALUE)
	{
		ATLTRACE(L"Get %s capability failed!!!", dev_info->name.c_str());
		return false;
	}

	if (::GetCommProperties(handle, &prop) == FALSE)
	{
		::CloseHandle(handle);
		return false;
	}

	::CloseHandle(handle);

	sub_type.SetWindowText(L"");
	sub_type.SetWindowText(DumpComCapaSubType(prop.dwProvSubType));

	capa_baudrate.ResetContent();
	DumpComCapaBaud(capa_baudrate, prop.dwMaxBaud);
	capa_baudrate.SetCurSel(0);

	settable_params.ResetContent();
	DumpComCapaSettableParams(settable_params, prop.dwSettableParams);
	settable_params.SetCurSel(0);

	dev_capas.ResetContent();
	DumpComCapaProvCapabilities(dev_capas, prop.dwProvCapabilities);
	dev_capas.SetCurSel(0);

	return true;
}

LRESULT CPortSetupDlg::OnInitDialog(CWindow wndFocus, LPARAM lInitPara)
{
	CenterWindow();
	// set icons
	HICON hIcon = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDI_CONFIG_ICON), 
		IMAGE_ICON, ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
	SetIcon(hIcon, TRUE);
	HICON hIconSmall = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDI_CONFIG_ICON), 
		IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
	SetIcon(hIconSmall, FALSE);

	DEV_BROADCAST_DEVICEINTERFACE notify_filter = { 0 };

	notify_filter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
	notify_filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
	notify_filter.dbcc_classguid = g_SerialBusGuid;

	dev_notify_handle_ = ::RegisterDeviceNotification(m_hWnd, 
		&notify_filter, DEVICE_NOTIFY_WINDOW_HANDLE);

	ATLASSERT(dev_notify_handle_ != NULL);

	InitControl();
	return TRUE;
}

void CPortSetupDlg::OnClose()
{
	if (dev_notify_handle_ != NULL)
	{
		if (::UnregisterDeviceNotification(dev_notify_handle_) != FALSE)
			dev_notify_handle_ = NULL;
		EndDialog(0);
	}
}

LRESULT CPortSetupDlg::OnOpen(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	CString str;
	if (usb_dev_list_.size() > 0)
	{
		port_combo_.GetLBText(port_combo_.GetCurSel(), str);
		std::vector<USB_DevInfo>::iterator itr = std::find_if(usb_dev_list_.begin(),
			usb_dev_list_.end(), FindByName(str));
		ATLASSERT(itr != usb_dev_list_.end());
		GetComCapability(&(*itr));
	}
	return 0;
}

LRESULT CPortSetupDlg::OnCloseCmd(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	EndDialog(wID);
	return 0;
}

LRESULT CPortSetupDlg::OnSelectChange(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
	if (usb_dev_list_.size() > 0)
	{
		CString str;
		int sel = port_combo_.GetCurSel();
		if (GetDeviceRegistryProperty(str, SPDRP_DEVICEDESC, &usb_dev_list_[sel]))
			dev_desc_edit_.SetWindowText(str.GetBuffer());
		if (GetDeviceRegistryProperty(str, SPDRP_MFG, &usb_dev_list_[sel]))
			dev_mfg_edit_.SetWindowText(str);
		CString tmp;
		port_combo_.GetWindowText(tmp);
	}
	return 0;
}

LRESULT CPortSetupDlg::OnDeviceChange(UINT nEventType, DWORD dwData)
{
	const DEV_BROADCAST_HDR *broadcast_hdr = reinterpret_cast<const DEV_BROADCAST_HDR*>(dwData);
	const DEV_BROADCAST_PORT *broadcast_port;
	CString cur_name;
	int cur_sel = -1;
	switch (nEventType)
	{
	case DBT_DEVICEREMOVECOMPLETE:
		// Device is gone
		cur_sel = port_combo_.GetCurSel();
		if (cur_sel != CB_ERR)
			port_combo_.GetLBText(cur_sel, cur_name);
		if (broadcast_hdr != NULL && broadcast_hdr->dbch_devicetype == DBT_DEVTYP_PORT)
		{
			broadcast_port = reinterpret_cast<const DEV_BROADCAST_PORT*>(dwData);
			if (broadcast_port != NULL)
			{
				std::vector<USB_DevInfo>::iterator itr = std::find_if(usb_dev_list_.begin(),
					usb_dev_list_.end(), FindByName(broadcast_port->dbcp_name));
				std::vector<USB_DevInfo>::iterator erase_itr = itr;
				if (itr != usb_dev_list_.end())
				{
					ATLTRACE(L"Delete %s \n", (*itr).name.c_str());
					// User was already remove the COM port
					int err = port_combo_.DeleteItem((*itr).index);
					// If remove current select item, just select first item (0)
					if ((*itr).index == cur_sel)
						cur_sel = 0;
#ifdef _DEBUG
					if (err == CB_ERR)
						ATLTRACE(L"Error!!! Delete port_combo_ (%s) item error \n", (*itr).name.c_str());
#endif // _DEBUG
					// Item was been deleted, index must be updata
					while (itr != usb_dev_list_.end())
					{
						--((*itr).index);
						++itr;
					}
					usb_dev_list_.erase(erase_itr);
					if (cur_sel != 0)
					{
						itr = std::find_if(usb_dev_list_.begin(),
							usb_dev_list_.end(), FindByName(cur_name));
						cur_sel = (*itr).index;
					}
					port_combo_.SetCurSel(cur_sel);
				}
			}
		}
		break;
	case DBT_DEVICEARRIVAL:
		// System detected a new device
		cur_sel = port_combo_.GetCurSel();
		if (cur_sel != CB_ERR)
			port_combo_.GetLBText(cur_sel, cur_name);
		if (broadcast_hdr != NULL && broadcast_hdr->dbch_devicetype == DBT_DEVTYP_PORT)
		{
			broadcast_port = reinterpret_cast<const DEV_BROADCAST_PORT*>(dwData);
			if (broadcast_port != NULL)
			{
				std::vector<USB_DevInfo>::iterator itr = std::find_if(usb_dev_list_.begin(),
					usb_dev_list_.end(), FindByName(broadcast_port->dbcp_name));
				if (itr == usb_dev_list_.end())
				{
					port_combo_.ResetContent();
					icon_list_.Destroy();
					usb_dev_list_.erase(usb_dev_list_.begin(), usb_dev_list_.end());
					GetSysPort();

					if (cur_sel != CB_ERR)
					{
						itr = std::find_if(usb_dev_list_.begin(),
							usb_dev_list_.end(), FindByName(cur_name));
						cur_sel = (*itr).index;
					}
				}
			}
		}
		if (cur_sel != CB_ERR)
			port_combo_.SetCurSel(cur_sel);
		else
			port_combo_.SetCurSel(0);
		break;
	}
	return 0;
}

void CPortSetupDlg::GetSysPort()
{
	if (GetSerialDeviceInfo(usb_dev_list_))
	{
		// Sort by number
		std::sort(usb_dev_list_.begin(), usb_dev_list_.end(), SortByName());

		HICON icon = ::LoadIcon(ModuleHelper::GetResourceInstance(),
			MAKEINTRESOURCE(IDI_ICON1));
		ATLASSERT(icon != NULL);

		BOOL ret;
		ICONINFO icon_info;

		ret = ::GetIconInfo(icon, &icon_info);
		ATLASSERT(ret == TRUE);

		ret = icon_list_.Create(icon_info.yHotspot * 2,
			icon_info.yHotspot * 2, 
			ILC_COLOR32,
			0, 
			static_cast<DWORD>(usb_dev_list_.size()));

		ATLASSERT(ret == TRUE);

		for (std::vector<USB_DevInfo>::size_type i = 0 ; i < usb_dev_list_.size() ; ++i)
			icon_list_.AddIcon(icon);

		port_combo_.SetImageList(icon_list_);

		for (std::vector<USB_DevInfo>::size_type i = 0 ; i < usb_dev_list_.size() ; ++i)
		{
			InsertPortItem(usb_dev_list_[i].name,
				static_cast<std::vector<USB_DevInfo>::size_type>(i), 0);
			usb_dev_list_[i].index = static_cast<int>(i);
		}

		port_combo_.SetCurSel(0);

		CString str;
		if (GetDeviceRegistryProperty(str, SPDRP_DEVICEDESC, &usb_dev_list_[0]))
			dev_desc_edit_.SetWindowText(str);

		if (GetDeviceRegistryProperty(str, SPDRP_MFG, &usb_dev_list_[0]))
			dev_mfg_edit_.SetWindowText(str);
	}
}

int CPortSetupDlg::InsertPortItem(const std::wstring &str, int num_item, int num_icon) 
{
	COMBOBOXEXITEM item = { 0 };
	item.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
	item.pszText = const_cast<LPTSTR>(str.c_str());
	item.cchTextMax = static_cast<int>(str.length());
	item.iItem = num_item;
	item.iImage = num_icon;
	item.iSelectedImage = num_icon;
	return port_combo_.InsertItem(&item);
}

void CPortSetupDlg::InitControl()
{
	port_combo_.Attach(GetDlgItem(IDC_COMCOMBO));
	baudrate_combo_.Attach(GetDlgItem(IDC_BAUDRATE_COMBO));
	dev_desc_edit_.Attach(GetDlgItem(IDC_DEVICEDESC_EDIT));
	dev_mfg_edit_.Attach(GetDlgItem(IDC_MFG_EDIT));

	capa_baudrate.Attach(GetDlgItem(IDC_DEV_INFO_MAX_BAUDRATE_COMBO));
	settable_params.Attach(GetDlgItem(IDC_DEV_INFO_SETTABLE_PARAMS_COMBO));
	sub_type.Attach(GetDlgItem(IDC_DEV_SUB_TYPE_EDIT));
	dev_capas.Attach(GetDlgItem(IDC_DEV_INFO_CAPAS_COMBO));

	dev_desc_edit_.SetReadOnly();
	dev_mfg_edit_.SetReadOnly();

	GetSysPort();
	for (int i = 0 ; i < _countof(g_PortBaudrate) ; ++i)
		baudrate_combo_.InsertString(i, g_PortBaudrate[i]);
	baudrate_combo_.SetCurSel(0);
}

